using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using space_matters.Utilities;
using Microsoft.Xna.Framework.Audio;

namespace space_matters
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game : Microsoft.Xna.Framework.Game
    {
        #region Constants

        /// <summary>
        /// Designed window width.
        /// </summary>
        public const int WINDOW_WIDTH = 800;

        /// <summary>
        /// Designed window height.
        /// </summary>
        public const int WINDOW_HEIGHT = 600;

        #endregion

        #region Factory

        /// <summary>
        /// Singleton game instance.
        /// </summary>
        private static Game _game;

        /// <summary>
        /// Retrieve the singleton game instance.
        /// </summary>
        /// <returns>The game instance.</returns>
        public static Game GetGame()
        {
            return _game ?? (_game = new Game());
        }

        #endregion

        #region Constructors

        private Game()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        #endregion

        #region Type Definitions

        private enum GameState
        {
            Loading,
            TitleScreen,
            Playing,
        }

        #endregion

        #region Static Elements

        private static Random _random = new Random();

        #endregion

        #region Private Elements

        #region XNA Graphics Elements

        /// <summary>
        /// Keeps track of the XNA graphics manager.
        /// </summary>
        private GraphicsDeviceManager _graphics;

        /// <summary>
        /// Keeps track of the <see cref="SpriteBatch"/> to which we draw.
        /// </summary>
        private SpriteBatch _spriteBatch;

        #endregion

        #region Game Elements

        /// <summary>
        /// Keeps track of the state of the <see cref="Game"/> finite state 
        /// machine.
        /// </summary>
        private GameState _state;

        private List<Element> _elements;
        private List<Star> _stars;
        private List<Planet> _planets;

        private Texture2D _overlayTint;

        public float FadeTime;
        public float TotalFadeTime = 1;

        private bool _clicked;

        private SoundEffectInstance _clickSound;

        private int _oldChance;

        #endregion

        #endregion

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            _graphics.PreferredBackBufferWidth = WINDOW_WIDTH;
            _graphics.PreferredBackBufferHeight = WINDOW_HEIGHT;
            _graphics.ApplyChanges();

            IsMouseVisible = true;

            _state = GameState.Loading;

            Window.Title = "Orbis";

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            base.UnloadContent();

            if (_spriteBatch != null)
                _spriteBatch.Dispose();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            var mouseState = Mouse.GetState();

            switch (_state)
            {
                case GameState.Loading:

                    if (Assets.Loaded)
                    {
                        _state = GameState.TitleScreen;
                        CreateWorld();

                        if (MediaPlayer.State != MediaState.Playing)
                        {
                            MediaPlayer.IsRepeating = true;
                            MediaPlayer.Volume = 1f;
                            MediaPlayer.Play(Assets.Get<Song>("Background Music"));
                        }

                        _overlayTint = DrawUtils.CreateFilledRectangle(_spriteBatch.GraphicsDevice, WINDOW_WIDTH,
                                                                       WINDOW_HEIGHT, Color.White, Color.White);
                    }
                    else
                    {
                        Assets.LoadOne();
                    }
                    
                    break;

                case GameState.TitleScreen:

                    var state = Mouse.GetState();

                    if (state.LeftButton == ButtonState.Pressed)
                    {
                        _state = GameState.Playing;
                    }

                    break;

                case GameState.Playing:

                    foreach (var dust in _elements)
                    {
                        dust.MaxVelocity = mouseState.LeftButton == ButtonState.Pressed ? 100 : 15;

                        dust.Update(gameTime);
                    }

                    var almostDeadStars = _stars.FindAll(star => star.IsAlmostDead && !star.AlmostDied);

                    foreach (var star in almostDeadStars)
                    {
                        star.AlmostDied = true;

                        var explodeSound = Assets.Get<SoundEffect>("Star Explosion");
                        explodeSound.Play();
                    }

                    var deadStars = _stars.FindAll(star => star.IsCompletelyDead);

                    #region Star Explosion Logic

                    foreach (var star in deadStars)
                    {
                        //FadeTime = 1;
                        //TotalFadeTime = 1;
                        var counter = star.Mass + star.Mass/2;
                        switch (star.Type)
                        {
                            case Star.StarType.Small:
                                while (counter >= 1)
                                {
                                    if (counter >= 2)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.ForestGreen * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Carbon, true));
                                        counter -= 2;
                                    }

                                    if (counter >= 1)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.Yellow * 0.5f, 1, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Hydrogen, true));
                                        counter -= 1;
                                    }
                                }
                                break;

                            case Star.StarType.Medium:
                                while (counter >= 1)
                                {
                                    if (counter >= 3)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble()*5*star.Radius - 2.5f*star.Radius),
                                            (float)(_random.NextDouble()*5*star.Radius - 2.5f*star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();

                                        _elements.Add(new Element(Color.SkyBlue * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Oxygen, true));
                                        counter -= 3;
                                    }

                                    if (counter >= 2)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();

                                        _elements.Add(new Element(Color.ForestGreen * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Carbon, true));
                                        counter -= 2;
                                    }

                                    if (counter >= 1)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble()*5*star.Radius - 2.5f*star.Radius),
                                            (float)(_random.NextDouble()*5*star.Radius - 2.5f*star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.Yellow * 0.5f, 1, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Hydrogen, true));
                                        counter -= 1;
                                    }

                                    if (counter >= 1)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.Yellow * 0.5f, 1, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Hydrogen, true));
                                        counter -= 1;
                                    }
                                }
                                break;

                            case Star.StarType.Large:
                                while (counter >= 1)
                                {
                                    if (counter >= 5)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.DarkRed * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Iron, true));
                                        counter -= 5;
                                    }

                                    if (counter >= 3)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.SkyBlue * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Oxygen, true));
                                        counter -= 3;
                                    }

                                    if (counter >= 3)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.SkyBlue * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Oxygen, true));
                                        counter -= 3;
                                    }

                                    if (counter >= 2)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.ForestGreen * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Carbon, true));
                                        counter -= 2;
                                    }

                                    if (counter >= 2)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble()*5*star.Radius - 2.5f*star.Radius),
                                            (float)(_random.NextDouble()*5*star.Radius - 2.5f*star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.ForestGreen * 0.5f, 2, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Carbon, true));
                                        counter -= 2;
                                    }

                                    if (counter >= 1)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.Yellow * 0.5f, 1, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Hydrogen, true));
                                        counter -= 1;
                                    }

                                    if (counter >= 1)
                                    {
                                        var position = star.Pos + new Vector2(
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius),
                                            (float)(_random.NextDouble() * 5 * star.Radius - 2.5f * star.Radius));

                                        var direction = position - star.Pos;
                                        direction.Normalize();


                                        _elements.Add(new Element(Color.Yellow * 0.5f, 1, 4,
                                            position, direction * (float)_random.NextDouble() * 30, 30, Element.Type.Hydrogen, true));
                                        counter -= 1;
                                    }
                                }
                                break;
                        }
                    }

                    #endregion

                    _stars.RemoveAll(star => star.IsCompletelyDead);

                    foreach (var star in _stars)
                    {
                        star.MaxVelocity = mouseState.LeftButton == ButtonState.Pressed ? 70 : 30;

                        star.Update(gameTime);
                    }

                    foreach (var planet in _planets)
                    {
                        planet.MaxVelocity = mouseState.LeftButton == ButtonState.Pressed ? 70 : 30;

                        planet.Update(gameTime);
                    }

                    if (mouseState.LeftButton == ButtonState.Pressed)
                    {
                        if (!_clicked)
                        {
                            _clicked = true;

                            var clickSound = Assets.Get<SoundEffect>("Click");
                            _clickSound = clickSound.CreateInstance();
                            _clickSound.IsLooped = true;
                            _clickSound.Volume = 0.8f;
                            MediaPlayer.Volume = 0.6f;
                            _clickSound.Play();
                        }

                        foreach (var dust in _elements)
                        {
                            // Allow gravity to work with wrapping.
                            var otherX = (dust.Pos.X < WINDOW_WIDTH / 2f)
                                        ? dust.Pos.X + WINDOW_WIDTH
                                        : dust.Pos.X - WINDOW_WIDTH;

                            var otherY = (dust.Pos.Y < WINDOW_HEIGHT / 2f)
                                        ? dust.Pos.Y + WINDOW_HEIGHT
                                        : dust.Pos.Y - WINDOW_HEIGHT;

                            var realX = dust.Pos.X;
                            var realY = dust.Pos.Y;

                            if (Math.Abs(mouseState.X - dust.Pos.X) > Math.Abs(mouseState.X - otherX))
                                realX = otherX;

                            if (Math.Abs(mouseState.Y - dust.Pos.Y) > Math.Abs(mouseState.Y - otherY))
                                realY = otherY;

                            var diff = new Vector2(mouseState.X - realX, mouseState.Y - realY);

                            if (!(Math.Abs(diff.X) < dust.Radius) || !(Math.Abs(diff.Y) < dust.Radius))
                                dust.ApplyForce(diff / diff.LengthSquared() * 2500 / new Vector2((float)dust.Mass, (float)dust.Mass));
                                //dust.Pos += diff / diff.LengthSquared() * 37 / new Vector2((float)dust.Mass, (float)dust.Mass);
                        }

                        foreach (var star in _stars)
                        {
                            if (star.IsDead)
                                continue;

                            // Allow gravity to work with wrapping.
                            var otherX = (star.Pos.X < WINDOW_WIDTH / 2f)
                                        ? star.Pos.X + WINDOW_WIDTH
                                        : star.Pos.X - WINDOW_WIDTH;

                            var otherY = (star.Pos.Y < WINDOW_HEIGHT / 2f)
                                        ? star.Pos.Y + WINDOW_HEIGHT
                                        : star.Pos.Y - WINDOW_HEIGHT;

                            var realX = star.Pos.X;
                            var realY = star.Pos.Y;

                            if (Math.Abs(mouseState.X - star.Pos.X) > Math.Abs(mouseState.X - otherX))
                                realX = otherX;

                            if (Math.Abs(mouseState.Y - star.Pos.Y) > Math.Abs(mouseState.Y - otherY))
                                realY = otherY;

                            var diff = new Vector2(mouseState.X - realX, mouseState.Y - realY);

                            if (!(Math.Abs(diff.X) < star.Radius) || !(Math.Abs(diff.Y) < star.Radius))
                                star.ApplyForce(diff / diff.LengthSquared() * 3000 / new Vector2((float)star.Mass, (float)star.Mass));
                            //dust.Pos += diff / diff.LengthSquared() * 37 / new Vector2((float)dust.Mass, (float)dust.Mass);
                        }

                        foreach (var planet in _planets)
                        {
                            // Allow gravity to work with wrapping.
                            var otherX = (planet.Pos.X < WINDOW_WIDTH / 2f)
                                        ? planet.Pos.X + WINDOW_WIDTH
                                        : planet.Pos.X - WINDOW_WIDTH;

                            var otherY = (planet.Pos.Y < WINDOW_HEIGHT / 2f)
                                        ? planet.Pos.Y + WINDOW_HEIGHT
                                        : planet.Pos.Y - WINDOW_HEIGHT;

                            var realX = planet.Pos.X;
                            var realY = planet.Pos.Y;

                            if (Math.Abs(mouseState.X - planet.Pos.X) > Math.Abs(mouseState.X - otherX))
                                realX = otherX;

                            if (Math.Abs(mouseState.Y - planet.Pos.Y) > Math.Abs(mouseState.Y - otherY))
                                realY = otherY;

                            var diff = new Vector2(mouseState.X - realX, mouseState.Y - realY);

                            if (!(Math.Abs(diff.X) < planet.Radius) || !(Math.Abs(diff.Y) < planet.Radius))
                                planet.ApplyForce(diff / diff.LengthSquared() * 9000 / new Vector2((float)planet.Mass, (float)planet.Mass));
                            //dust.Pos += diff / diff.LengthSquared() * 37 / new Vector2((float)dust.Mass, (float)dust.Mass);
                        }
                    }
                    else
                    {
                        if (_clicked)
                        {
                            _clicked = false;

                            _clickSound.Stop();
                            MediaPlayer.Volume = 1f;
                        }
                    }

                    // Do collision detection!

                    for (var i = 0; i < _elements.Count; i++)
                    {
                        var deleted = false;

                        var dust = _elements[i];

                        // Elements with Elements
                        for (var j = i + 1; j < _elements.Count; j++)
                        {
                            var dust2 = _elements[j];
                            if (!Collide(dust, dust2)) 
                                continue;
                            
                            var newPos = new Vector2(((float) dust.Mass*dust.Pos.X + (float) dust2.Mass*dust2.Pos.X)/
                                                        (float) (dust.Mass + dust2.Mass),
                                                     ((float) dust.Mass*dust.Pos.Y + (float) dust2.Mass*dust2.Pos.Y)/
                                                        (float) (dust.Mass + dust2.Mass));

                            if (dust.Inactive > TimeSpan.Zero || dust2.Inactive > TimeSpan.Zero)
                            {
                                //dust.Velocity = -1 * dust.Velocity;
                                //dust2.Velocity = -1 * dust2.Velocity;

                                // Collision resolution: make sure that dust does not go into one another.
                                ResolveCollision(dust, dust2);
                                continue;
                            }

                            // Form into different thing:
                            // Larger element
                            if (dust.type == dust2.type && dust.Radius < 12 && dust2.Radius < 12)
                            {
                                if (dust.Radius >= 8) dust.Color = new Color(dust.Color.R, dust.Color.G, dust.Color.B, 1f);
                                _elements.Add(new Element(dust.Color, dust.Mass + dust2.Mass,
                                                      Math.Sqrt(dust.Radius * dust.Radius + dust2.Radius * dust2.Radius),
                                                      newPos, new Vector2((float)_random.NextDouble() * 15, (float)_random.NextDouble() * 15), dust.MaxVelocity, dust.type));
                                _elements.Remove(dust); i--;
                                _elements.Remove(dust2);

                                deleted = true;
                                break;
                            }
                            // Star
                            else if ((dust.Radius >= 8 && dust2.Radius >= 8) && ((dust.type == Element.Type.Hydrogen && dust2.type == Element.Type.Carbon) ||
                                (dust.type == Element.Type.Carbon && dust2.type == Element.Type.Hydrogen)))
                            {
                                _stars.Add(new Star(TimeSpan.FromSeconds(20), Color.White, dust.Mass + dust2.Mass,
                                                    Math.Sqrt(dust.Radius * dust.Radius + dust2.Radius * dust2.Radius),
                                                    newPos, new Vector2((float)_random.NextDouble() * 15, (float)_random.NextDouble() * 15), 5));
                                _elements.Remove(dust); i--;
                                _elements.Remove(dust2);

                                var starSound = Assets.Get<SoundEffect>("Making Star");
                                starSound.Play();

                                deleted = true;
                                break;
                            }
                            // Planet
                            else if (dust.Radius >= 8 && dust2.Radius >= 8) {
                                Planet.PlanetType planetType;
                                // Blue Planet
                                if ((dust.type == Element.Type.Hydrogen && dust2.type == Element.Type.Oxygen) ||
                                    (dust.type == Element.Type.Oxygen && dust2.type == Element.Type.Hydrogen))
                                        planetType = Planet.PlanetType.BluePlanet;
                                // Gas Planet
                                else if ((dust.type == Element.Type.Carbon && dust2.type == Element.Type.Oxygen) ||
                                    (dust.type == Element.Type.Oxygen && dust2.type == Element.Type.Carbon))
                                        planetType = Planet.PlanetType.GasPlanet;
                                // Ice Planet
                                else  if ((dust.type == Element.Type.Carbon && dust2.type == Element.Type.Iron) ||
                                    (dust.type == Element.Type.Iron && dust2.type == Element.Type.Carbon))
                                        planetType = Planet.PlanetType.IcePlanet;
                                // Red Planet
                                else if ((dust.type == Element.Type.Hydrogen && dust2.type == Element.Type.Iron) ||
                                    (dust.type == Element.Type.Iron && dust2.type == Element.Type.Hydrogen))
                                    planetType = Planet.PlanetType.RedPlanet;
                                else
                                {
                                    break;
                                }

                                _planets.Add(new Planet(planetType, dust.Mass + dust2.Mass,
                                                    Math.Sqrt(dust.Radius * dust.Radius + dust2.Radius * dust2.Radius),
                                                    newPos, new Vector2((float)_random.NextDouble() * 15, (float)_random.NextDouble() * 15)));
                                _elements.Remove(dust); i--;
                                _elements.Remove(dust2);

                                deleted = true;
                                break;
                            }
                    
                            //dust.Velocity = -1 * dust.Velocity;
                            //dust2.Velocity = -1 * dust2.Velocity;

                            // Collision resolution: make sure that dust does not go into one another.
                            ResolveCollision(dust, dust2);
                        }

                        // Elements with stars
                        if (!deleted)
                        {
                            for (var s = 0; s < _stars.Count; s++)
                            {
                                var star = _stars[s];

                                if (star.IsDead)
                                    continue;

                                if (!Collide(dust, star))
                                    continue;

                                var newPos = new Vector2(((float) dust.Mass*dust.Pos.X + (float) star.Mass*star.Pos.X)/
                                                         (float) (dust.Mass + star.Mass),
                                                         ((float) dust.Mass*dust.Pos.Y + (float) star.Mass*star.Pos.Y)/
                                                         (float) (dust.Mass + star.Mass));

                                ////////if (dust.type == dust2.type)
                                //{
                                star.Mass += dust.Mass;
                                star.Radius = Math.Sqrt(star.Radius*star.Radius + dust.Radius*dust.Radius);

                                /*_stars.Add(new Star(Color.Violet, dust.Mass + star.Mass,
                                                      Math.Sqrt(dust.Radius * dust.Radius + star.Radius * star.Radius),
                                                      newPos, new Vector2((float)_random.NextDouble() * 15, (float)_random.NextDouble() * 15)));
                                */
                                _elements.Remove(dust);
                                deleted = true;
                                i--;
                                break;
                                //_stars.Remove(star); s--;
                                //}
                            }
                        }

                        if (!deleted)
                        {
                            for (var p = 0; p < _planets.Count; p++)
                            {
                                var planet = _planets[p];
                                if (!Collide(dust, planet))
                                    continue;

                                //ResolveCollision(dust, planet);
                            }
                        }
                    }
                    
                    for (var i = 0; i < _stars.Count; i++)
                    {
                        var star = _stars[i];

                        if (star.IsDead)
                            continue;

                        // Stars with stars
                        for (var j = i + 1; j < _stars.Count; j++)
                        {
                            var star2 = _stars[j];
                            if (!Collide(star, star2))
                                continue;

                            var newPos = new Vector2(((float)star.Mass * star.Pos.X + (float)star2.Mass * star2.Pos.X) /
                                                        (float)(star.Mass + star2.Mass),
                                                     ((float)star.Mass * star.Pos.Y + (float)star2.Mass * star2.Pos.Y) /
                                                        (float)(star.Mass + star2.Mass));

                            star.Pos = newPos;
                            star.Radius = Math.Sqrt(star.Radius*star.Radius + star2.Radius*star2.Radius);
                            star.Mass += star2.Mass;
                            _stars.Remove(star2);
                            j--;
                        }

                        // Stars with planets
                        for (var k = 0; k < _planets.Count; k++)
                        {
                            var planet = _planets[k];
                            if (!Collide(star, planet))
                                continue;

                            var newPos = new Vector2(((float)star.Mass * star.Pos.X + (float)planet.Mass * planet.Pos.X) /
                                                        (float)(star.Mass + planet.Mass),
                                                     ((float)star.Mass * star.Pos.Y + (float)planet.Mass * planet.Pos.Y) /
                                                        (float)(star.Mass + planet.Mass));

                            star.Pos = newPos;
                            star.Radius = Math.Sqrt(star.Radius * star.Radius + planet.Radius * planet.Radius);
                            star.Mass += planet.Mass;
                            _planets.Remove(planet);
                            k--;
                        }
                    }

                    // Planets with planets 
                    for (var i = 0; i < _planets.Count; i++)
                    {
                        var planet = _planets[i];
                        for (var j = i + 1; j < _planets.Count; j++)
                        {
                            var planet2 = _planets[j];
                            // Gas planets combine
                            if (!Collide(planet, planet2))
                                continue;
                            if ((planet._type == Planet.PlanetType.GasPlanet && planet2._type == Planet.PlanetType.GasPlanet) ||
                               (planet._type == Planet.PlanetType.BluePlanet && planet2._type == Planet.PlanetType.BluePlanet))
                            {
                                var newPos = new Vector2(((float)planet.Mass * planet.Pos.X + (float)planet2.Mass * planet2.Pos.X) /
                                                            (float)(planet.Mass + planet2.Mass),
                                                         ((float)planet.Mass * planet.Pos.Y + (float)planet2.Mass * planet2.Pos.Y) /
                                                            (float)(planet.Mass + planet2.Mass));

                                planet.Pos = newPos;
                                planet.Radius = Math.Sqrt(planet.Radius * planet.Radius + planet2.Radius * planet2.Radius);
                                planet.Mass += planet2.Mass;
                                _planets.Remove(planet2);
                                j--;

                                var planetSound = Assets.Get<SoundEffect>("Making Planet");
                                planetSound.Play();
                            }
                            // Gas + rock = habitable
                            else if ((planet._type == Planet.PlanetType.BluePlanet && planet2._type == Planet.PlanetType.IcePlanet) ||
                                     (planet2._type == Planet.PlanetType.BluePlanet && planet._type == Planet.PlanetType.IcePlanet) ||
                                     (planet._type == Planet.PlanetType.BluePlanet && planet2._type == Planet.PlanetType.RedPlanet) ||
                                     (planet2._type == Planet.PlanetType.BluePlanet && planet._type == Planet.PlanetType.RedPlanet) ||
                                     (planet._type == Planet.PlanetType.GasPlanet && planet2._type == Planet.PlanetType.IcePlanet) ||
                                     (planet2._type == Planet.PlanetType.GasPlanet && planet._type == Planet.PlanetType.IcePlanet) ||
                                     (planet._type == Planet.PlanetType.GasPlanet && planet2._type == Planet.PlanetType.RedPlanet) ||
                                     (planet2._type == Planet.PlanetType.GasPlanet && planet._type == Planet.PlanetType.RedPlanet))
                            {
                                var newPos = new Vector2(((float)planet.Mass * planet.Pos.X + (float)planet2.Mass * planet2.Pos.X) /
                                                            (float)(planet.Mass + planet2.Mass),
                                                         ((float)planet.Mass * planet.Pos.Y + (float)planet2.Mass * planet2.Pos.Y) /
                                                            (float)(planet.Mass + planet2.Mass));


                                _planets.Add(new Planet(Planet.PlanetType.EarthPlanet, planet.Mass + planet2.Mass,
                                                      Math.Sqrt(planet.Radius * planet.Radius + planet2.Radius * planet2.Radius),
                                                      newPos, new Vector2((float)_random.NextDouble() * 15, (float)_random.NextDouble() * 15), planet.MaxVelocity));
                                _planets.Remove(planet); i--;
                                _planets.Remove(planet2); j--;
                            }
                            else
                            {
                                ResolveCollision(planet, planet2);

                                var bounceSound = Assets.Get<SoundEffect>("Planet Together");
                                bounceSound.Play();
                            }
                        }
                    }

                    // Do some gravitational stuff.

                    foreach (var star in _stars)
                    {
                        if (star.IsDead)
                            continue;

                        foreach (var dust in _elements)
                        {
                            var diff = star.Pos - dust.Pos;
                            var dist = diff.LengthSquared();
                            diff.Normalize();
                            dust.ApplyForce((float)(1 * dust.Mass * star.Mass / dist) * diff);
                        }

                        foreach (var otherStar in _stars)
                        {
                            if (otherStar == star)
                                continue;

                            var diff = star.Pos - otherStar.Pos;
                            var dist = diff.LengthSquared();
                            diff.Normalize();
                            otherStar.ApplyForce((float)(1 * otherStar.Mass * star.Mass / dist) * diff);
                        }

                        foreach (var planet in _planets)
                        {
                            var diff = star.Pos - planet.Pos;
                            var dist = diff.LengthSquared();
                            diff.Normalize();
                            planet.ApplyForce((float)(1 * planet.Mass * star.Mass / dist) * diff);
                        }
                    }
                        
                    break;
            }

            base.Update(gameTime);
        }

        private static void ResolveCollision(Matter obj1, Matter obj2)
        {
            var dn = obj1.Pos - obj2.Pos;
 
            // The distance between the balls
            var delta = dn.Length();
 
            // The normal vector of the collision plane
            dn.Normalize();
 
            // The tangential vector of the collision plane
            var dt = new Vector2(dn.Y, -dn.X);
 
            // avoid division by zero
            if (Math.Abs(delta) < 0.00001)
            {
                obj2.Pos += new Vector2(0.01f,0);
                return;
            }
 
            //the masses of the two balls
            var m1 = obj1.Mass;
            var m2 = obj2.Mass;
            var m = m1 + m2;
 
            // minimum translation distance to push balls apart after intersecting
            var mT = dn * (float)((obj1.Radius + obj2.Radius - delta));
 
            // push the balls apart proportional to their mass
            obj1.Pos += (mT * (float)(m2/m));
            obj2.Pos -= (mT * (float)(m1/m));
 
            // the velocity vectors of the balls before the collision
            var v1 = obj1.Velocity;
            var v2 = obj2.Velocity;
 
            // split the velocity vector of the first ball into a normal and a
            // tangential component in respect of the collision plane
            var v1N = dn * Vector2.Dot(v1, dn);
            var v1T = dt * Vector2.Dot(v1, dt);
 
            // split the velocity vector of the second ball into a normal and a
            // tangential component in respect of the collision plane
            var v2N = dn * Vector2.Dot(v2, dn);
            var v2T = dt * Vector2.Dot(v2, dt);
 
            // calculate new velocity vectors of the balls, the tangential component
            // stays the same, the normal component changes analog to the 1-Dimensional case
            obj1.Velocity = v1T + dn * (float)((m1 - m2) / m * v1N.Length() + 2 * m2 / m * v2N.Length());
            obj2.Velocity = v2T - dn * (float)((m2 - m1) / m * v2N.Length() + 2 * m1 / m * v1N.Length());
        }

        /// <summary>
        /// Generate initial world conditions.
        /// </summary>
        private void CreateWorld()
        {
            _elements = new List<Element>();
            _stars = new List<Star>();
            _planets = new List<Planet>();

            for (var i = 0; i < 65; i++)
            {
                _elements.Add(new Element(Color.Yellow*0.5f, 1, 4, new Vector2(_random.Next(WINDOW_WIDTH), _random.Next(WINDOW_HEIGHT)),
                    new Vector2((float)_random.NextDouble() * 15, (float)_random.NextDouble() * 15), 30));
            }

            for (var i = 0; i < 65; i++)
            {
                _elements.Add(new Element(Color.ForestGreen*0.5f, 2, 4, new Vector2(_random.Next(WINDOW_WIDTH), _random.Next(WINDOW_HEIGHT)),
                    Vector2.Zero, 30, Element.Type.Carbon));
            }

            //_planets.Add(new Planet(Planet.PlanetType.IcePlanet, 1, 16, new Vector2(_random.Next(WINDOW_WIDTH), _random.Next(WINDOW_HEIGHT))));
            //_planets.Add(new Planet(Planet.PlanetType.BluePlanet, 1, 16, new Vector2(_random.Next(WINDOW_WIDTH), _random.Next(WINDOW_HEIGHT))));

            /*
            for (var i = 0; i < 2; i++)
            {
                _planets.Add(new Planet(Planet.PlanetType.RedPlanet, 12, 16, new Vector2(_random.Next(WINDOW_WIDTH), _random.Next(WINDOW_HEIGHT))));
            }

            for (var i = 0; i < 2; i++)
            {
                _planets.Add(new Planet(Planet.PlanetType.EarthPlanet, 16, 20, new Vector2(_random.Next(WINDOW_WIDTH), _random.Next(WINDOW_HEIGHT))));
            }
             */
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            switch (_state)
            {
                case GameState.Loading:
                    DrawLoadingScreen();
                    break;

                case GameState.TitleScreen:
                    DrawTitleScreen();
                    break;

                case GameState.Playing:

                    _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

                    var x = Math.Max(Math.Min(25, (Mouse.GetState().X - WINDOW_WIDTH / 2) * 50 / WINDOW_WIDTH), -25);
                    var y = Math.Max(Math.Min(25, (Mouse.GetState().Y - WINDOW_HEIGHT / 2) * 50 / WINDOW_HEIGHT), -25);

                    var background = Assets.Get<Texture2D>("Background");

                    _spriteBatch.Draw(background, new Rectangle(x - 25, y - 25, WINDOW_WIDTH + 50, WINDOW_HEIGHT + 50), Color.White);

                    foreach (var dust in _elements)
                    {
                        dust.Draw(_spriteBatch, gameTime);
                    }
                    foreach (var star in _stars)
                    {
                        star.Draw(_spriteBatch, gameTime);
                    }
                    foreach (var planet in _planets)
                    {
                        planet.Draw(_spriteBatch, gameTime);
                    }

                    _spriteBatch.End();

                    DrawLifeBar();

                    if (FadeTime > 0)
                    {
                        DrawFlash(FadeTime);
                        FadeTime -= TotalFadeTime/100;
                    }
                    else if (FadeTime < 0)
                        FadeTime = 0;

                    _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

                    _spriteBatch.Draw(_overlayTint, Vector2.Zero, Color.MediumPurple*0.08f);

                    _spriteBatch.End();

                    break;
            }

            base.Draw(gameTime);
        }

        private Boolean Collide(Matter a, Matter b)
        {
            if (a == b) return false; // No collision of objects are the same
            if (Distance(a.Pos, b.Pos) < a.Radius + b.Radius)
                return true;
            return false;
        }

        private double Distance(Vector2 a, Vector2 b)
        {
            return Math.Sqrt((b.X - a.X) * (b.X - a.X) + (b.Y - a.Y) * (b.Y - a.Y));
        }

        #region Drawing Methods

        private void DrawFlash(float lerp)
        {
            _spriteBatch.Begin();
            _spriteBatch.Draw(_overlayTint, Vector2.Zero, Color.White * lerp);

            if (TotalFadeTime == 5) {
                Texture2D t = Assets.Get<Texture2D>("End Globe");
                Texture2D g = Assets.Get<Texture2D>("End Glow");
                _spriteBatch.Draw(t, new Rectangle(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 - 100, 200, 200), Color.White * lerp);
                _spriteBatch.Draw(g, new Rectangle(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 - 100, 200, 200), Color.White * lerp);
            }

            _spriteBatch.End();
        }
        private void DrawLifeBar()
        {
            var position = new Vector2(WINDOW_WIDTH - 300, WINDOW_HEIGHT - 120);

            int chance = 0; // Chance of life progress
            if (_stars.Count > 0) chance = 1;
            if (_planets.Count > 0) chance = 2;
            int rockPlanets = 0;
            int gasPlanets = 0;
            foreach (var planet in _planets)
            {
                if (planet._type == Planet.PlanetType.GasPlanet || planet._type == Planet.PlanetType.BluePlanet)
                    gasPlanets++;
                if (planet._type == Planet.PlanetType.IcePlanet || planet._type == Planet.PlanetType.RedPlanet)
                    rockPlanets++;
                if (planet._type == Planet.PlanetType.EarthPlanet)
                    chance = 4;
            }
            if (chance != 4 && rockPlanets > 0 && gasPlanets > 0)
                chance = 3;

            // if planet has perfect atmosphere, chance = 3

            if (chance > _oldChance)
            {
                var lifeSound = Assets.Get<SoundEffect>("Life Potential Up");
                var lifeSoundManager = lifeSound.CreateInstance();
                lifeSoundManager.Volume = 0.1f;
                lifeSound.Play();
            }
            else if (chance < _oldChance)
            {
                var lifeSound = Assets.Get<SoundEffect>("Life Potential Down");
                var lifeSoundManager = lifeSound.CreateInstance();
                lifeSoundManager.Volume = 0.1f;
                lifeSound.Play();
            }

            _spriteBatch.Begin();
            var lifebar = Assets.Get<Texture2D>("Life Bar");
            //var smallfont = Assets.Get<SpriteFont>("Small font");
            _spriteBatch.Draw(lifebar, new Rectangle((int) position.X, (int) position.Y, 250, 93), new Rectangle(chance*400,0, 400, lifebar.Height), Color.White);
            //_spriteBatch.DrawString(smallfont, "Chance of Life", position, new Color(0.2f, 0.2f, 0.2f, 0.5f));
            _spriteBatch.End();

            _oldChance = chance;

        }
        /// <summary>
        /// Draw the loading resources screen.
        /// </summary>
        private void DrawLoadingScreen()
        {
            Debug.Assert(_spriteBatch != null, "The sprite batch we use should not be null.");

            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

            var font = Assets.Get<SpriteFont>("Harrington");

            var progress = Assets.Get<Texture2D>("Loading Bar");
            var barWidth = progress.Width;
            var barHeight = progress.Height / 2;
            var doneSize = (int)(Assets.PercentLoaded / 100.0 * barWidth + 0.5);
            var progressPosition = new Vector2(455, 158);

            _spriteBatch.Draw(Assets.Get<Texture2D>("Title Screen"), new Rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT), Color.White);

            _spriteBatch.DrawString(font, "Orbis", new Vector2(450, 70), new Color(113, 125, 49));

            _spriteBatch.Draw(progress, progressPosition, new Rectangle(0, barHeight, barWidth, barHeight), Color.White);
            _spriteBatch.Draw(progress, progressPosition, new Rectangle(0, 0, doneSize, barHeight), Color.White);

            _spriteBatch.End();
        }

        private void DrawTitleScreen()
        {
            Debug.Assert(_spriteBatch != null, "The sprite batch we use should not be null.");

            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

            var font = Assets.Get<SpriteFont>("Harrington");
            var font2 = Assets.Get<SpriteFont>("Verdana");

            _spriteBatch.Draw(Assets.Get<Texture2D>("Title Screen"), new Rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT),
                              Color.White);

            _spriteBatch.DrawString(font, "Orbis", new Vector2(450, 70), new Color(113, 125, 49));
            _spriteBatch.DrawString(font2, "Click to begin...", new Vector2(458, 158), Color.White);

            _spriteBatch.End();
        }


        #endregion
    }
}
